package ua.kharkiv.khpi.tarasenko.diploma;

import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import ua.kharkiv.khpi.tarasenko.diploma.analysis.approach.AnalysisApproach;

public class GeneralAnalysis implements AnalysisApproach{

	private int min_sup = 0;

	public GeneralAnalysis() {}

	public Map<Sequence, Integer> analyze(PseudoDB db, int min_sup) {
		this.min_sup = min_sup;
		return getFreqSeq(db, Item.getItem(""));
	}

	private PseudoDB project(PseudoDB db, Item prefix) {

		PseudoDB projection = new PseudoDB();

		for (Sequence row : db.getRows()) {
			projection.addRow(project(row, prefix));
		}

		return projection;
	}

	private Sequence project(Sequence seqToProject, Item prefix) {

		Sequence result = new Sequence();
		if (prefix == null || prefix.isEmpty()) {
			result = seqToProject;
		}

		int index = seqToProject.indexOf(prefix);

		if (index != -1) {
			result = seqToProject.subsequence(index + 1);
		}
		return result;
	}

	/**
	 * Returns list of frequent items in database
	 * 
	 * @param db
	 * @return f_list
	 */

	private Map<Item, Integer> getFList(PseudoDB db) {

		Map<Item, Integer> items = new HashMap<Item, Integer>();

		for (Sequence seq : db.getRows()) {
			// TODO optimise? check if it already in collection
			for (Item item : seq.getItems()) {
				items.put(item, new Integer(0));
				// System.out.println(item);
			}
		}

		System.out.println("Get all items. " + new Date());

		int count = 0;
		for (Sequence row : db.getRows()) {
			if (count % 1000 == 0) {
				System.out.println(db.getRows().size() - count);
			}

			Set<Item> checkedItemsInRow = new HashSet<Item>(row.getItems()
					.size());
			for (Item item : row.getItems()) {
				if (!checkedItemsInRow.contains(item)) {
					Integer currSup = items.get(item);
					System.out.println(currSup);
					currSup = currSup + 1;
					items.put(item, currSup);
					checkedItemsInRow.add(item);
				}
			}

			count++;
		}

		System.out.println("support for all counted. " + new Date());

		Map<Item, Integer> f_list = new HashMap<Item, Integer>();

		for (Map.Entry<Item, Integer> entry : items.entrySet()) {
			if (entry.getValue() >= min_sup) {
				f_list.put(entry.getKey(), entry.getValue());
			}
		}
		System.out.println("f_list found. " + new Date());

		return f_list;
	}

	private Map<Sequence, Integer> join(Map<Sequence, Integer> seqs,
			Item element) {
		Map<Sequence, Integer> joinedSeq = new HashMap<Sequence, Integer>();
		if (!element.isEmpty()) {
			for (Map.Entry<Sequence, Integer> entry : seqs.entrySet()) {
				Sequence newRow = new Sequence();
				newRow.addItem(element);
				newRow.addSequence(entry.getKey());
				joinedSeq.put(newRow, entry.getValue());
			}

			return joinedSeq;
		} else {
			return seqs;
		}
	}

	private Map<Sequence, Integer> getFreqSeq(PseudoDB db, Item currFItem) {

		System.out.println("getFS. " + new Date());
		Map<Sequence, Integer> nextLevelFreqSeq = new HashMap<Sequence, Integer>();
		Map<Item, Integer> f_list = getFList(db);
		db = prune(db, f_list);

		// check condition
		if (db.getRows().size() > 1) {
			for (Item freqItem : f_list.keySet()) {
				PseudoDB projDB = project(db, freqItem);
				nextLevelFreqSeq.putAll(getFreqSeq(projDB, freqItem));
			}
		}

		// Put f_list into nextLevelFreqSeq
		// TODO do it prettier
		for (Map.Entry<Item, Integer> entry : f_list.entrySet()) {
			Sequence seq = new Sequence();
			seq.addItem(entry.getKey());
			nextLevelFreqSeq.put(seq, entry.getValue());
		}
		nextLevelFreqSeq = join(nextLevelFreqSeq, currFItem);

		return nextLevelFreqSeq;
	}

	/**
	 * Creates new database from which all infrequent items are deleted
	 * 
	 * @param dbToPrune
	 * @param f_list
	 * @return
	 */
	private PseudoDB prune(PseudoDB dbToPrune, Map<Item, Integer> f_list) {
		PseudoDB resultDB = new PseudoDB();

		// TODO optimise?
		for (Sequence seq : dbToPrune.getRows()) {
			Sequence prunedSeq = new Sequence();
			for (Item item : seq.getItems()) {
				if (f_list.keySet().contains(item)) {
					prunedSeq.addItem(item);
				}
			}
			resultDB.addRow(prunedSeq);
		}
		return resultDB;
	}
}
